#Region "Using Directives"
Imports System
Imports System.Text
#End Region

Namespace C4F.DevKit.Bluetooth.SerialPortProfile

    ''' <summary>
    ''' Implements Serial Port Profile.
    ''' </summary>
    Public Class BluetoothSPPManager
        Inherits System.ComponentModel.Component

#Region "Private Fields"

        ''' <summary>
        ''' Name of the port which will be used for communication.
        ''' </summary>
        Private m_portName As String

        ''' <summary>
        ''' SerialPort object representing the port which will be used for 
        ''' communication.
        ''' </summary>
        Private WithEvents serialPort As System.IO.Ports.SerialPort

        ''' <summary>
        ''' NetworkStream which will be used for communication.
        ''' </summary>
        Private m_networkStream As System.Net.Sockets.NetworkStream

        ''' <summary>
        ''' Thread which will listen for DataDetectedFromRemoteDevice event.
        ''' </summary>
        Private serialCommEventThread As System.Threading.Thread

#Region "Serial Port Settings"

        ''' <summary>
        ''' Serial baud rate.
        ''' </summary>
        Private m_baudRate As Integer = DEFAULT_BAUD_RATE

        ''' <summary>
        ''' Standard length of data bits per byte.
        ''' </summary>
        Private m_dataBits As Integer = DEFAULT_DATA_BITS

        ''' <summary>
        ''' Parity-checking protocol. 
        ''' </summary>
        Private m_parity As System.IO.Ports.Parity = DEFAULT_PARITY

        ''' <summary>
        ''' A byte that replaces invalid bytes in a data stream when a parity
        ''' error occurs. 
        ''' </summary>
        Private m_parityReplace As Byte = DEFAULT_PARITY_REPLACE

        ''' <summary>
        ''' The standard number of stopbits per byte. 
        ''' </summary>
        Private m_stopBits As System.IO.Ports.StopBits = DEFAULT_STOP_BITS

        ''' <summary>
        ''' The size of the SerialPort input buffer. 
        ''' </summary>
        Private m_readBufferSize As Integer = DEFAULT_READBUFFER_SIZE

        ''' <summary>
        ''' The number of milliseconds before a time-out occurs when a read
        ''' operation does not finish. 
        ''' </summary>
        Private m_readTimeout As Integer = DEFAULT_READ_TIMEOUT

        ''' <summary>
        ''' The size of the SerialPort output buffer. 
        ''' </summary>
        Private m_writeBufferSize As Integer = DEFAULT_WRITEBUFFER_SIZE

        ''' <summary>
        ''' The number of milliseconds before a time-out occurs when a write
        ''' operation does not finish.
        ''' </summary>
        Private m_writeTimeout As Integer = DEFAULT_WRITE_TIMEOUT

        ''' <summary>
        ''' The byte encoding for pre- and post-transmission conversion of text.
        ''' </summary>
        Private m_encoding As Encoding = ASCIIEncoding.ASCII

#End Region

#End Region

#Region "Private Constants"

        ''' <summary>
        ''' The maximum length of data bits per byte.
        ''' </summary>
        Private Const MAX_DATA_BITS As Integer = 8

        ''' <summary>
        ''' The minimum length of data bits per byte.
        ''' </summary>
        Private Const MIN_DATA_BITS As Integer = 5

#End Region

#Region "Public Constants"

        'Default values.

        ''' <summary>
        ''' The default value for serial baud rate is 9600 bits per second (bps).
        ''' </summary>
        Public Const DEFAULT_BAUD_RATE As Integer = 9600

        ''' <summary>
        ''' The default value for standard length of data bits per byte is 8.
        ''' </summary>
        Public Const DEFAULT_DATA_BITS As Integer = 8

        ''' <summary>
        ''' The default value for Parity-checking protocol is None.
        ''' </summary>
        Public Const DEFAULT_PARITY As System.IO.Ports.Parity = System.IO.Ports.Parity.None

        ''' <summary>
        ''' The default value for the byte that replaces invalid bytes in a
        ''' data stream when a parity error occurs is 0x3f.
        ''' </summary>
        Public Const DEFAULT_PARITY_REPLACE As Byte = 63

        ''' <summary>
        ''' The default value for the standard number of stopbits per byte is 1.
        ''' </summary>
        Public Const DEFAULT_STOP_BITS As System.IO.Ports.StopBits = System.IO.Ports.StopBits.One

        ''' <summary>
        ''' The default value for the size of the SerialPort input buffer is 
        ''' 4096.
        ''' </summary>
        Public Const DEFAULT_READBUFFER_SIZE As Integer = 4096

        ''' <summary>
        ''' The default value for the number of milliseconds before a time-out 
        ''' occurs when a read operation does not finish is InfiniteTimeout.
        ''' </summary>
        Public Const DEFAULT_READ_TIMEOUT As Integer = -1

        ''' <summary>
        ''' The default value for the size of the SerialPort output buffer is
        ''' 2048.
        ''' </summary>
        Public Const DEFAULT_WRITEBUFFER_SIZE As Integer = 2048

        ''' <summary>
        ''' The default value for the number of milliseconds before a time-out
        ''' occurs when a write operation does not finish is InfiniteTimeout.
        ''' </summary>
        Public Const DEFAULT_WRITE_TIMEOUT As Integer = -1

        ''' <summary>
        ''' The value for InfiniteTimeout. 
        ''' </summary>
        Public Const INFINITE_TIMEOUT As Integer = -1

#End Region

#Region "Public Properties"

        ''' <summary>
        ''' Gets or sets the name of the port.
        ''' </summary>
        <System.ComponentModel.Category(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                    Resources.SPPConstants.Category_SerialPortSettings)> _
        <System.ComponentModel.Description(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                           Resources.SPPConstants.Desc_PortName)> _
        Public Property PortName() As String
            Get
                Return Me.m_portName
            End Get

            Set(ByVal value As String)
                If Me.serialPort IsNot Nothing Then
                    Throw New InvalidOperationException(SPPResources.Error_SP_PortSet)
                Else
                    Me.m_portName = value
                End If

            End Set

        End Property

        ''' <summary>
        ''' Gets the status of the port (Opened/Closed).
        ''' </summary>
        <System.ComponentModel.Browsable(False)> _
        Public ReadOnly Property IsPortOpen() As Boolean

            Get
                If Me.serialPort Is Nothing Then
                    Return False
                Else
                    Return Me.serialPort.IsOpen
                End If

            End Get

        End Property

        ''' <summary>
        ''' Gets or sets the NetworkStream used for communicating with a remote device.
        ''' </summary>
        <System.ComponentModel.Browsable(False)> _
        Public Property NetworkStream() As System.Net.Sockets.NetworkStream

            Get
                Return Me.m_networkStream
            End Get

            Set(ByVal value As System.Net.Sockets.NetworkStream)

                With Me
                    .m_networkStream = value
                    If .m_networkStream IsNot Nothing Then

                        .m_networkStream.WriteTimeout = .m_readTimeout
                        .m_networkStream.ReadTimeout = .m_writeTimeout

                    End If

                End With

            End Set

        End Property

#Region "Serial Port Settings"

        ''' <summary>
        ''' Gets or sets the serial baud rate. The default value is 9600 bits
        ''' per second (bps).
        ''' </summary>
        <System.ComponentModel.Category(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                    Resources.SPPConstants.Category_SerialPortSettings)> _
        <System.ComponentModel.Description(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                           Resources.SPPConstants.Desc_BaudRate)> _
        Public Property BaudRate() As Integer

            Get
                Return Me.m_baudRate
            End Get

            Set(ByVal value As Integer)
                If value <= 0 Then
                    Throw New ArgumentOutOfRangeException("BaudRate", SPPResources. _
                                                          Error_SP_InvalidBaudRate)
                End If

                With Me
                    .m_baudRate = value
                    If .serialPort IsNot Nothing Then
                        .serialPort.BaudRate = value
                    End If

                End With
            End Set

        End Property

        ''' <summary>
        ''' Gets or sets the standard length of data bits per byte. 
        ''' The range of values for this field is from 5 through 8. 
        ''' The default value is 8.
        ''' </summary>
        <System.ComponentModel.Category(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                    Resources.SPPConstants.Category_SerialPortSettings)> _
        <System.ComponentModel.Description(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                           Resources.SPPConstants.Desc_DataBits)> _
        Public Property DataBits() As Integer

            Get
                Return Me.m_dataBits
            End Get

            Set(ByVal value As Integer)
                If (value < MIN_DATA_BITS) OrElse (value > MAX_DATA_BITS) Then
                    Throw New ArgumentOutOfRangeException("DataBits", SPPResources. _
                                                          Error_SP_DataBitsRange)
                End If

                With Me
                    .m_dataBits = value
                    If .serialPort IsNot Nothing Then
                        .serialPort.DataBits = value
                    End If

                End With

            End Set

        End Property

        ''' <summary>
        ''' Gets or sets the byte encoding for pre- and post-transmission
        ''' conversion of text. 
        ''' The default is ASCIIEncoding.
        ''' </summary>
        <System.ComponentModel.Browsable(False)> _
        Public Property Encoding() As Encoding

            Get
                Return Me.m_encoding
            End Get

            Set(ByVal value As Encoding)
                If value Is Nothing Then
                    Me.m_encoding = ASCIIEncoding.ASCII
                Else

                    If ((Not (TypeOf value Is ASCIIEncoding) AndAlso Not (TypeOf value Is UTF8Encoding)) AndAlso (Not (TypeOf value Is UnicodeEncoding) AndAlso Not (TypeOf value Is UTF32Encoding))) AndAlso (((value.CodePage >= 50000) AndAlso (value.CodePage <> 54936)) OrElse (Not ((value.GetType().Assembly).Equals(GetType(String).Assembly)))) Then
                        Throw New ArgumentException(SPPResources.Error_SP_EncodingNotSupported, "Encoding")
                    End If

                    With Me
                        .m_encoding = value
                        If .serialPort IsNot Nothing Then
                            .serialPort.Encoding = value
                        End If

                    End With
                End If

            End Set

        End Property

        ''' <summary>
        ''' Gets or sets the Parity-checking protocol. The default value is 
        ''' None.
        ''' </summary>
        <System.ComponentModel.Category(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                    Resources.SPPConstants.Category_SerialPortSettings)> _
        <System.ComponentModel.Description(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                           Resources.SPPConstants.Desc_Parity)> _
        Public Property Parity() As System.IO.Ports.Parity

            Get
                Return Me.m_parity
            End Get

            Set(ByVal value As System.IO.Ports.Parity)
                If Not System.Enum.IsDefined(GetType(System.IO.Ports.Parity), value) Then
                    Throw New ArgumentOutOfRangeException("Parity")
                End If

                With Me
                    .m_parity = value
                    If .serialPort IsNot Nothing Then
                        .serialPort.Parity = value
                    End If

                End With

            End Set

        End Property

        ''' <summary>
        ''' Gets or sets the byte that replaces invalid bytes in a data stream 
        ''' when a parity error occurs. 
        ''' The default value is 0x3f.
        ''' </summary>
        <System.ComponentModel.Category(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                    Resources.SPPConstants.Category_SerialPortSettings)> _
        <System.ComponentModel.Description(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                           Resources.SPPConstants.Desc_ParityReplace)> _
        Public Property ParityReplace() As Byte
            Get
                Return Me.m_parityReplace
            End Get

            Set(ByVal value As Byte)
                With Me
                    .m_parityReplace = value
                    If .serialPort IsNot Nothing Then
                        .serialPort.ParityReplace = value
                    End If

                End With

            End Set

        End Property

        ''' <summary>
        ''' Gets or sets the size of the SerialPort input buffer. The default
        ''' value is 4096. If the port is open, ReadBufferSize will not be set.
        ''' </summary>
        <System.ComponentModel.Category(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                    Resources.SPPConstants.Category_SerialPortSettings)> _
        <System.ComponentModel.Description(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                           Resources.SPPConstants.Desc_ReadBufferSize)> _
        Public Property ReadBufferSize() As Integer
            Get
                Return Me.m_readBufferSize
            End Get

            Set(ByVal value As Integer)
                If value <= 0 Then
                    Throw New ArgumentOutOfRangeException("ReadBufferSize")
                End If

                With Me
                    If .serialPort IsNot Nothing Then
                        If IsPortOpen Then
                            .m_readBufferSize = .serialPort.ReadBufferSize
                        Else

                            ' If the port is open, ReadBufferSize cannot be set. 
                            ' So set the portsetting values to the current port settings.
                            ' If the port is closed, ReadBufferSize can be set.
                            .m_readBufferSize = value
                            .serialPort.ReadBufferSize = .m_readBufferSize
                        End If

                    Else
                        .m_readBufferSize = value
                    End If

                End With

            End Set

        End Property

        ''' <summary>
        ''' Gets or sets the number of milliseconds before a time-out occurs
        ''' when a read operation does not finish. 
        ''' InfiniteTimeout is the default value.
        ''' </summary>
        <System.ComponentModel.Category(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                    Resources.SPPConstants.Category_SerialPortSettings)> _
        <System.ComponentModel.Description(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                           Resources.SPPConstants.Desc_ReadTimeout)> _
        Public Property ReadTimeout() As Integer
            Get
                Return Me.m_readTimeout
            End Get

            Set(ByVal value As Integer)
                If (value < 0) AndAlso (value <> -1) Then
                    Throw New ArgumentOutOfRangeException("ReadTimeout")
                End If

                With Me
                    .m_readTimeout = value
                    If .serialPort IsNot Nothing Then
                        .serialPort.ReadTimeout = value
                    End If

                    If m_networkStream IsNot Nothing Then
                        m_networkStream.WriteTimeout = .m_readTimeout
                    End If

                End With

            End Set

        End Property

        ''' <summary>
        ''' Gets or sets the standard number of stopbits per byte. The default 
        ''' value is 1.
        ''' </summary>
        <System.ComponentModel.Category(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                    Resources.SPPConstants.Category_SerialPortSettings)> _
        <System.ComponentModel.Description(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                           Resources.SPPConstants.Desc_StopBits)> _
        Public Property StopBits() As System.IO.Ports.StopBits
            Get
                Return Me.m_stopBits
            End Get

            Set(ByVal value As System.IO.Ports.StopBits)
                If Not System.Enum.IsDefined(GetType(System.IO.Ports.StopBits), value) Then
                    Throw New ArgumentOutOfRangeException("StopBits")
                End If

                With Me
                    .m_stopBits = value
                    If .serialPort IsNot Nothing Then
                        .serialPort.StopBits = value
                    End If

                End With

            End Set

        End Property

        ''' <summary>
        ''' Gets or sets the size of the SerialPort output buffer. The default
        ''' value is 2048. If the port is open, WriteBufferSize will not be set.
        ''' </summary>
        <System.ComponentModel.Category(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                    Resources.SPPConstants.Category_SerialPortSettings)> _
        <System.ComponentModel.Description(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                           Resources.SPPConstants.Desc_WriteBufferSize)> _
        Public Property WriteBufferSize() As Integer
            Get
                Return Me.m_writeBufferSize
            End Get

            Set(ByVal value As Integer)
                If value <= 0 Then
                    Throw New ArgumentOutOfRangeException("WriteBufferSize")
                End If

                If Me.serialPort IsNot Nothing Then
                    If IsPortOpen Then
                        Me.m_writeBufferSize = Me.serialPort.WriteBufferSize

                    Else
                        ' If the port is open, WriteBufferSize cannot be set. 
                        ' So set the portsetting values to the current port settings.
                        ' If the port is closed, WriteBufferSize can be set.
                        Me.m_writeBufferSize = value
                        Me.serialPort.WriteBufferSize = Me.m_writeBufferSize
                    End If

                Else
                    Me.m_writeBufferSize = value
                End If

            End Set

        End Property

        ''' <summary>
        ''' Gets or sets the number of milliseconds before a time-out occurs
        ''' when a write operation does not finish. 
        ''' InfiniteTimeout is the default value.
        ''' </summary>
        <System.ComponentModel.Category(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                    Resources.SPPConstants.Category_SerialPortSettings)> _
        <System.ComponentModel.Description(C4F.DevKit.Bluetooth.SerialPortProfile. _
                                           Resources.SPPConstants.Desc_WriteTimeout)> _
        Public Property WriteTimeout() As Integer
            Get
                Return Me.m_writeTimeout
            End Get

            Set(ByVal value As Integer)
                If (value <= 0) AndAlso (value <> -1) Then
                    Throw New ArgumentOutOfRangeException("WriteTimeout")
                End If

                With Me
                    .m_writeTimeout = value
                    If .serialPort IsNot Nothing Then
                        .serialPort.WriteTimeout = value
                    End If

                    If m_networkStream IsNot Nothing Then
                        m_networkStream.ReadTimeout = .m_writeTimeout
                    End If

                End With

            End Set

        End Property

#End Region

#End Region

#Region "Constructors and Destructors"

        ''' <summary>
        ''' This constructor just creates a new SPPManager object. 
        ''' </summary>
        Public Sub New()
        End Sub
        ' <summary>
        ' This constructor takes port name and network stream as parameters
        ' and initializes all fields of this class.
        ' </summary>
        ' <param name="portName">Name of the port which will be used for
        ' communication.</param>
        ' <param name="networkStream">NetworkStream which will be used for
        ' communication.</param>
        ' <exception cref="ArgumentNullException">portName passed is an empty
        ' string or a null reference or networkStream is a null reference.
        ' </exception>
        Public Sub New(ByVal portName As String, ByVal networkStream As System.Net. _
                        Sockets.NetworkStream)
            If (String.IsNullOrEmpty(portName) OrElse String.IsNullOrEmpty(portName.Trim())) Then
                Throw New ArgumentNullException("portName")
            End If

            If (networkStream Is DBNull.Value) Then
                Throw New ArgumentNullException("networkStream")
            End If

            ' Set the port name and network stream.
            Me.m_portName = portName
            Me.m_networkStream = networkStream
        End Sub
        Protected Overrides Sub Finalize()
            Try
                ' Destructor.

                If Me.serialPort IsNot Nothing Then
                    Me.serialPort.Close()
                    Me.serialPort.Dispose()
                End If
                ' If the thread that is listening to the network stream is alive,
                ' abort it.
                If Me.serialCommEventThread IsNot Nothing Then
                    If Me.serialCommEventThread.IsAlive Then
                        Me.serialCommEventThread.Abort()
                    End If
                End If
                Me.Dispose(False)
            Finally
                MyBase.Finalize()
            End Try
        End Sub

#End Region

#Region "Public Events and Delegates"

        ''' <summary>
        ''' This represents the method that will be raised when data is detected
        ''' on the port.
        ''' </summary>
        ''' <param name="sender">The sender of this event.</param>
        ''' <param name="e">EventArgs object containing information about the 
        ''' event.</param>
        Public Delegate Sub DataDetectedOnSerialPortEventHandler(ByVal sender As Object, ByVal e As EventArgs)

        ''' <summary>
        ''' This represents the method that will be raised when data is detected
        ''' on the NetworkStream.
        ''' </summary>
        ''' <param name="sender">The sender of this event.</param>
        ''' <param name="e">EventArgs object containing information about the 
        ''' event.</param>
        Public Delegate Sub DataDetectedFromRemoteDeviceEventHandler(ByVal sender As Object, ByVal e As EventArgs)

        ''' <summary>
        ''' This event will be raised when incoming data is detected on the port
        ''' (to be sent to the NetworkStream).
        ''' </summary>
        Public Event DataDetectedOnSerialPort As DataDetectedOnSerialPortEventHandler

        ''' <summary>
        ''' This event will be raised when incoming data is detected on the 
        ''' NetworkStream (to be sent to the port).
        ''' </summary>
        Public Event DataDetectedFromRemoteDevice As DataDetectedFromRemoteDeviceEventHandler

#End Region

#Region "Private Functions/Methods"

        ''' <summary>
        ''' Sets up the serial port.
        ''' </summary>
        ''' <exception cref="InvalidOperationException">PortName is not set.</exception>
        Private Sub SetupSerialPort()
            If String.IsNullOrEmpty(Me.m_portName) OrElse String.IsNullOrEmpty(Me.m_portName.Trim()) Then
                Throw New InvalidOperationException(SPPResources.Error_SP_PortNameNotSet)
            End If

            ' Set up the port.
            Me.serialPort = New System.IO.Ports.SerialPort(Me.m_portName)

            ' Configure the port.
            ConfigurePort()
        End Sub

        ''' <summary>
        ''' Configures the serial port settings. 
        ''' Note that ReadBufferSize and WriteBufferSize will not be set if the
        ''' port is open.
        ''' </summary>
        ''' <exception cref="ArgumentNullException">PortSettings property has 
        ''' not been set.</exception>
        Private Sub ConfigurePort()
            With serialPort
                .BaudRate = Me.m_baudRate
                .DataBits = Me.m_dataBits

                If Me.m_encoding IsNot Nothing Then
                    .Encoding = Me.m_encoding
                End If

                .Parity = Me.m_parity
                .ParityReplace = Me.m_parityReplace
                .StopBits = Me.m_stopBits
                .ReadTimeout = Me.m_readTimeout

                If m_networkStream IsNot Nothing Then
                    m_networkStream.WriteTimeout = Me.m_readTimeout
                    m_networkStream.ReadTimeout = Me.m_writeTimeout
                End If

                .WriteTimeout = Me.m_writeTimeout

                If IsPortOpen Then
                    ' If the port is open, ReadBufferSize and WriteBufferSize 
                    ' cannot be set. So set the portsetting values to the 
                    ' current port settings.
                    Me.m_readBufferSize = .ReadBufferSize
                    Me.m_writeBufferSize = .WriteBufferSize
                Else
                    ' If the port is closed, ReadBufferSize and WriteBufferSize
                    ' can be set.
                    .ReadBufferSize = Me.m_readBufferSize
                    .WriteBufferSize = Me.m_writeBufferSize
                End If

            End With

        End Sub

        ''' <summary>
        ''' Helps the serialCommEventThread to listen to the NetworkStream.
        ''' If data is available, it raises a DataDetectedFromRemoteDevice 
        ''' event.
        ''' </summary>
        Private Sub ReceiveThread()
            While Me.IsPortOpen
                System.Threading.Thread.Sleep(100)

                ' Raise an event that data is detected on the Network stream.
                If Me.m_networkStream.DataAvailable Then
                    RaiseEvent DataDetectedFromRemoteDevice(Me, New EventArgs())
                End If

            End While

        End Sub

        ''' <summary>
        ''' This is the handler method for the serial port when an error is
        ''' received on the port. It raises an ErrorReceived event.
        ''' </summary>
        ''' <param name="sender">The sender of the event.</param>
        ''' <param name="e">Provides data for the serial port's ErrorReceived
        ''' event.</param>
        Private Sub serialPort_ErrorReceived(ByVal sender As Object, ByVal e As System.IO.Ports. _
                                    SerialErrorReceivedEventArgs) Handles serialPort.ErrorReceived
            Dim errorMessage As String = String.Empty

            Select Case e.EventType
                Case System.IO.Ports.SerialError.Frame
                    errorMessage = SPPResources.Error_SP_Frame
                    Exit Select
                Case System.IO.Ports.SerialError.Overrun

                    errorMessage = SPPResources.Error_SP_Overrun
                    Exit Select
                Case System.IO.Ports.SerialError.RXOver

                    errorMessage = SPPResources.Error_SP_RXOver
                    Exit Select
                Case System.IO.Ports.SerialError.RXParity

                    errorMessage = SPPResources.Error_SP_RXParity
                    Exit Select
                Case System.IO.Ports.SerialError.TXFull

                    errorMessage = SPPResources.Error_SP_TXFull
                    Exit Select
            End Select

            Throw New SPPException(SerialCommErrorType.SerialPortError, errorMessage)
        End Sub

        ''' <summary>
        ''' This is the handler method for the serial port when data is received
        ''' on the port. It raises a DataDetectedOnSerialPort event.
        ''' </summary>
        ''' <param name="sender">The sender of the event.</param>
        ''' <param name="e">Provides data for the serial port's DataReceived
        ''' event.</param>
        Private Sub serialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports. _
                                            SerialDataReceivedEventArgs) _
                                            Handles serialPort.DataReceived

            RaiseEvent DataDetectedOnSerialPort(sender, New EventArgs())
            ' Since data is detected on the serial port, raise an event.
        End Sub

#End Region

#Region "Public Functions/Methods"

        ''' <summary>
        ''' This method opens the specified port, configure it, and starts a 
        ''' thread that listens to the NetworkStream.
        ''' </summary>
        ''' <exception cref="SPPException">An error occurs while opening the
        ''' port. (Note that the inner exception contains details of the
        ''' reason for exception)</exception>
        ''' <exception cref="InvalidOperationException">networkStream is not set.</exception>
        Public Sub OpenPort()
            If Me.m_networkStream Is Nothing Then
                Throw New InvalidOperationException(SPPResources.Error_NS_NetworkStreamNotSet)
            End If

            Try
                If Me.serialPort Is Nothing Then
                    SetupSerialPort()
                    ' Setup the serial port using the specified port name.
                End If

                If Me.serialPort.IsOpen Then
                    Throw New InvalidOperationException(SPPResources.Error_SP_PortOpen)
                End If

                ' Open the port.
                Me.serialPort.Open()

                ' Start the thread that will listen to the NetworkStream.
                serialCommEventThread = New System.Threading.Thread(New System.Threading. _
                                                            ThreadStart(AddressOf ReceiveThread))
                serialCommEventThread.Start()
            Catch ex As Exception
                Throw New SPPException(SerialCommErrorType.SerialPortError, SPPResources. _
                                       Error_SP_OpenPort, ex)
            End Try

        End Sub

        ''' <summary>
        ''' This method closes the open port.
        ''' </summary>
        ''' <exception cref="SPPException">An error occurs while closing the
        ''' port. (Note that the inner exception contains details of the reason
        ''' for exception)</exception>
        Public Sub ClosePort()
            Try
                If Me.serialPort Is Nothing OrElse Not Me.serialPort.IsOpen Then
                    Throw New InvalidOperationException(SPPResources.Error_SP_PortNotOpen)
                End If

                Me.serialPort.Close()
            Catch ex As Exception
                Throw New SPPException(SerialCommErrorType.SerialPortError, SPPResources. _
                                       Error_SP_ClosePort, ex)
            End Try

        End Sub

        ''' <summary>
        ''' Reads numberOfBytesToSend bytes of data from the port and sends it
        ''' on the NetworkStream. It returns the number of bytes actually sent
        ''' to the remote device. The data that is sent is returned in the 
        ''' output buffer.
        ''' </summary>
        ''' <param name="numberOfBytesToSend">Number of bytes to be sent to the
        ''' remote device.</param>
        ''' <param name="buffer">Output buffer to store the data that was sent 
        ''' to the remote device.</param>
        ''' <returns>Number of bytes actually sent to the remote device.
        ''' </returns>
        ''' <exception cref="ArgumentException">numberOfBytesToSend is less
        ''' than zero or numberOfBytesToSend is greater than the serial port's
        ''' ReadBufferSize.</exception>
        ''' <exception cref="SPPException">An error occurs while sending data 
        ''' to the remote device. (Note that the inner exception contains 
        ''' details of the reason for exception)</exception>
        ''' <exception cref="InvalidOperationException">networkStream is not set.</exception>
        Public Function SendDataToRemoteDevice(ByVal numberOfBytesToSend As Integer, _
                                                ByRef buffer() As Byte) As Integer
            If numberOfBytesToSend < 0 Then
                Throw New ArgumentException(SPPResources.Error_PositiveNo, "numberOfBytesToSend")
            End If

            If numberOfBytesToSend > Me.serialPort.ReadBufferSize Then
                Throw New ArgumentException(SPPResources.Error_SP_numberOfBytesToSendToRemoteDevice, _
                                            "numberOfBytesToSend")
            End If

            If Me.serialPort Is Nothing OrElse Not Me.serialPort.IsOpen Then
                Throw New SPPException(SerialCommErrorType.SerialPortError, _
                                       SPPResources.Error_SP_SendDataToRemoteDevice, _
                                       New InvalidOperationException(SPPResources. _
                                                                     Error_SP_PortNotOpen))
            End If

            If Me.m_networkStream Is Nothing Then
                Throw New InvalidOperationException(SPPResources.Error_NS_NetworkStreamNotSet)
            End If

            If Not Me.m_networkStream.CanWrite Then
                Throw New SPPException(SerialCommErrorType.NetworkStreamError, SPPResources. _
                                       Error_SP_SendDataToRemoteDevice, _
                                       New InvalidOperationException(SPPResources. _
                                                                     Error_NS_ReadOnly))
            End If

            Dim numberOfBytesSent As Integer = 0
            buffer = New Byte(numberOfBytesToSend - 1) {}

            ' If no bytes have to be sent, return.
            If numberOfBytesToSend = 0 Then
                Return numberOfBytesSent
            End If

            Try

                ' Read data from the serial port into the buffer.
                numberOfBytesSent = Me.serialPort.Read(buffer, 0, numberOfBytesToSend)
            Catch ex As Exception
                Throw New SPPException(SerialCommErrorType.SerialPortError, SPPResources.Error_SP_SendDataToRemoteDevice, ex)
            End Try

            Try

                ' Write the data from the buffer to the NetworkStream.
                Me.m_networkStream.Write(buffer, 0, numberOfBytesSent)
            Catch ex As Exception
                Throw New SPPException(SerialCommErrorType.NetworkStreamError, SPPResources. _
                                       Error_SP_SendDataToRemoteDevice, ex)
            End Try

            ' Return the number of bytes sent to the remote device.
            Return numberOfBytesSent
        End Function

        ''' <summary>
        ''' Reads all the data present on the port and sends it on the Network
        ''' Stream. It returns the number of bytes actually sent to the remote
        ''' device. The data that is sent is returned in the output buffer.
        ''' </summary>
        ''' <param name="buffer">Output buffer to store the data that was sent 
        ''' to the remote device.</param>
        ''' <returns>Number of bytes actually sent to the remote device.
        ''' </returns>
        ''' <exception cref="SPPException">An error occurs while sending data
        ''' to the remote device. (Note that the inner exception contains 
        ''' details of the reason for exception)</exception>
        ''' <exception cref="InvalidOperationException">networkStream is not set.</exception>
        Public Function SendDataToRemoteDevice(ByRef buffer() As Byte) As Integer
            If Me.serialPort Is Nothing OrElse Not Me.serialPort.IsOpen Then
                Throw New SPPException(SerialCommErrorType.SerialPortError, SPPResources. _
                                       Error_SP_SendDataToRemoteDevice, _
                                       New InvalidOperationException(SPPResources. _
                                                                     Error_SP_PortNotOpen))
            End If

            If Me.m_networkStream Is Nothing Then
                Throw New InvalidOperationException(SPPResources.Error_NS_NetworkStreamNotSet)
            End If

            If Not Me.m_networkStream.CanWrite Then
                Throw New SPPException(SerialCommErrorType.NetworkStreamError, SPPResources. _
                                       Error_SP_SendDataToRemoteDevice, _
                                       New InvalidOperationException(SPPResources. _
                                                                     Error_NS_ReadOnly))
            End If

            Dim numberOfBytesSent As Integer = 0

            Try
                ' Read all existing data from the serial port into the buffer.
                buffer = System.Text.Encoding.ASCII.GetBytes(Me.serialPort.ReadExisting())
                numberOfBytesSent = buffer.Length
            Catch ex As Exception
                Throw New SPPException(SerialCommErrorType.SerialPortError, SPPResources. _
                                       Error_SP_SendDataToRemoteDevice, ex)
            End Try

            Try
                ' Write the data from the buffer to the NetworkStream.
                Me.m_networkStream.Write(buffer, 0, numberOfBytesSent)
            Catch ex As Exception
                Throw New SPPException(SerialCommErrorType.NetworkStreamError, SPPResources. _
                                       Error_SP_SendDataToRemoteDevice, ex)
            End Try

            ' Return the number of bytes sent to the remote device.
            Return numberOfBytesSent

        End Function

        ''' <summary>
        ''' Reads numberOfBytesToSend bytes of data from the NetworkStream and 
        ''' sends it on the serial port. It returns the number of bytes actually
        ''' sent to the serial port. The data that is sent is returned in the 
        ''' output buffer.
        ''' </summary>
        ''' <param name="numberOfBytesToSend">Number of bytes to be sent to the 
        ''' serial port.</param>
        ''' <param name="buffer">Output buffer to store the data that was sent 
        ''' to the serial port.</param>
        ''' <returns>Number of bytes actually sent to the serial port.</returns>
        ''' <exception cref="ArgumentException">numberOfBytesToSend is less
        ''' than zero or numberOfBytesToSend is greater than the serial port's
        ''' WriteBufferSize.</exception>
        ''' <exception cref="SPPException">An error occurs while sending data
        ''' to the serial port. (Note that the inner exception contains details
        ''' of the reason for exception)</exception>
        ''' <exception cref="InvalidOperationException">networkStream is not set.</exception>
        Public Function SendDataToSerialPort(ByVal numberOfBytesToSend As Integer, _
                                              ByRef buffer() As Byte) As Integer
            If numberOfBytesToSend < 0 Then
                Throw New ArgumentException(SPPResources.Error_PositiveNo, "numberOfBytesToSend")
            End If

            If numberOfBytesToSend > Me.serialPort.WriteBufferSize Then
                Throw New ArgumentException(SPPResources.Error_SP_numberOfBytesToSendToSerialPort, _
                                            "numberOfBytesToSend")
            End If

            If Me.serialPort Is Nothing OrElse Not Me.serialPort.IsOpen Then
                Throw New SPPException(SerialCommErrorType.SerialPortError, SPPResources. _
                                       Error_SP_SendDataToSerialPort, _
                                       New InvalidOperationException(SPPResources. _
                                                                     Error_SP_PortNotOpen))
            End If

            If Me.m_networkStream Is Nothing Then
                Throw New InvalidOperationException(SPPResources.Error_NS_NetworkStreamNotSet)
            End If

            If Not Me.m_networkStream.CanRead Then
                Throw New SPPException(SerialCommErrorType.NetworkStreamError, SPPResources. _
                                       Error_SP_SendDataToSerialPort, _
                                       New InvalidOperationException(SPPResources. _
                                                                     Error_NS_WriteOnly))
            End If

            Dim numberOfBytesSent As Integer = 0
            buffer = New Byte(numberOfBytesToSend - 1) {}

            ' If no data has to be read, return.
            If numberOfBytesToSend = 0 Then
                Return numberOfBytesSent
            End If

            Try
                ' Read data from the NetworkStream into the buffer.
                numberOfBytesSent = Me.m_networkStream.Read(buffer, 0, numberOfBytesToSend)
            Catch ex As Exception
                Throw New SPPException(SerialCommErrorType.NetworkStreamError, SPPResources. _
                                       Error_SP_SendDataToSerialPort, ex)
            End Try

            Try
                ' Write data from the buffer to the port.
                Me.serialPort.Write(buffer, 0, numberOfBytesSent)
            Catch ex As Exception
                Throw New SPPException(SerialCommErrorType.SerialPortError, SPPResources. _
                                       Error_SP_SendDataToSerialPort, ex)
            End Try

            ' Return the number of bytes sent to the serial port.
            Return numberOfBytesSent
        End Function

        ''' <summary>
        ''' Reads all the data present on the NetworkStream and sends it on the
        ''' serial port. It returns the number of bytes actually sent to the 
        ''' serial port. The data that is sent is returned in the output buffer.
        ''' </summary>
        ''' <param name="buffer">Output buffer to store the data that was sent 
        ''' to the serial port.</param>
        ''' <returns>Number of bytes actually sent to the serial port.</returns>
        ''' <exception cref="SPPException">An error occurs while sending data
        ''' to the serial port. (Note that the inner exception contains details
        ''' of the reason for exception)</exception>
        ''' <exception cref="InvalidOperationException">networkStream is not set.</exception>
        Public Function SendDataToSerialPort(ByRef buffer() As Byte) As Integer
            If Me.serialPort Is Nothing OrElse Not Me.serialPort.IsOpen Then
                Throw New SPPException(SerialCommErrorType.SerialPortError, SPPResources. _
                                       Error_SP_SendDataToSerialPort, _
                                       New InvalidOperationException(SPPResources. _
                                                                     Error_SP_PortNotOpen))
            End If

            If Me.m_networkStream Is Nothing Then
                Throw New InvalidOperationException(SPPResources.Error_NS_NetworkStreamNotSet)
            End If

            If Not Me.m_networkStream.CanRead Then
                Throw New SPPException(SerialCommErrorType.NetworkStreamError, SPPResources. _
                                       Error_SP_SendDataToSerialPort, _
                                       New InvalidOperationException(SPPResources. _
                                                                     Error_NS_WriteOnly))
            End If

            Dim numberOfBytesSent As Integer = 0

            Dim dataToSend As New System.IO.MemoryStream()
            Dim serialPortWriteBufferSize As Integer = Me.serialPort.WriteBufferSize

            Try
                While Me.m_networkStream.DataAvailable AndAlso dataToSend.Length < serialPortWriteBufferSize

                    ' Read a byte from the NetworkStream into the MemoryStream 
                    ' while there is data on the NetworkStream and the length 
                    ' of the data read is less than the port buffer size.
                    Dim byteRead As Integer = Me.m_networkStream.ReadByte()
                    If byteRead = -1 Then
                        Exit While

                    Else
                        dataToSend.WriteByte(CByte(byteRead))
                        ' If the byte read is -1, End of stream has been 
                        ' encountered. Stop reading from the network stream.
                    End If

                End While

            Catch ex As Exception
                Throw New SPPException(SerialCommErrorType.NetworkStreamError, SPPResources. _
                                       Error_SP_SendDataToSerialPort, ex)
            End Try

            ' Put the data received from the NetworkStream into the buffer.
            buffer = dataToSend.ToArray()
            numberOfBytesSent = Convert.ToInt32(dataToSend.Length)
            dataToSend.Close()

            Try

                ' Write the data in the buffer to the serial port.
                Me.serialPort.Write(buffer, 0, numberOfBytesSent)
            Catch ex As Exception
                Throw New SPPException(SerialCommErrorType.SerialPortError, SPPResources.Error_SP_SendDataToSerialPort, ex)
            End Try

            Return numberOfBytesSent

        End Function

#End Region
    End Class
End Namespace
